package web

import (
	"errors"
	regexp "github.com/dlclark/regexp2"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"go.uber.org/zap"
	"log"
	"net/http"
	"time"
	"webook/internal/domain"
	"webook/internal/service"
	ijwt "webook/internal/web/jwt"
)

const (
	emailRegexPattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
	// 和上面比起来，用 ` 看起来就比较清爽
	passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`
	bizLogin             = "login"
)

type UserHandler struct {
	emailRegexExp    *regexp.Regexp
	passwordRegexExp *regexp.Regexp
	svc              service.UserService
	codeSvc          service.CodeService
	ijwt.Handler
}

func NewUserHandler(svc service.UserService, codeSvc service.CodeService, hdl ijwt.Handler) *UserHandler {
	return &UserHandler{
		emailRegexExp:    regexp.MustCompile(emailRegexPattern, regexp.None),
		passwordRegexExp: regexp.MustCompile(passwordRegexPattern, regexp.None),
		svc:              svc, // service是传入的 保持依赖注入的风格
		codeSvc:          codeSvc,
		Handler:          hdl,
	}
}

func (h *UserHandler) RegisterRoutes(server *gin.Engine) {
	//RESTFUL风格 POST和PUT的区别不明确
	//server.POST("/user",h.SignUp)
	//server.PUT("/user",h.SignUp)
	//server.GET("/users/:username",h.Profile)
	//分组路由
	ug := server.Group("/users")
	//对应POST /users/signup
	ug.POST("/signup", h.SignUp)
	//ug.POST("/login", h.Login)
	ug.POST("/login", h.LoginJWT)
	ug.POST("/logout", h.LogoutJWT)
	ug.POST("/edit", h.Edit)
	ug.GET("/profile", h.Profile)
	ug.GET("/refresh_token", h.RefreshToken)
	//手机验证码登录相关功能
	ug.POST("/login_sms/code/send", h.SendSMSLoginCode)
	ug.POST("/login_sms", h.LoginSMS)
}

func (h *UserHandler) SignUp(ctx *gin.Context) {
	var err error
	defer func() {
		if err != nil {
			zap.L().Error("SignUp error", zap.Error(err))
		}
	}()
	type SignUpReq struct {
		Email           string `json:"email"`
		Password        string `json:"password"`
		ConfirmPassword string `json:"confirmPassword"`
	}
	var req SignUpReq
	//如果err不为空的话 Bind方法会返回错误
	if err = ctx.Bind(&req); err != nil {
		return
	}
	isEmail, err := h.emailRegexExp.MatchString(req.Email)
	if err != nil { //错误处理
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !isEmail {
		ctx.String(http.StatusOK, "非法邮箱格式")
		return
	}
	//确认两次密码是否一致 认为就是前端页面的相关校验 不应该上升到领域层面
	if req.Password != req.ConfirmPassword {
		ctx.String(http.StatusOK, "两次输入的密码不相同")
		return
	}
	isPassword, err := h.passwordRegexExp.MatchString(req.Password)
	if err != nil { //错误处理
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !isPassword {
		ctx.String(http.StatusOK, "密码必须包含数字、特殊字符，并且长度不能小于8")
		return
	}
	//todo 注意这里要改成 ctx.Request.Context() 10-8的bug
	err = h.svc.SignUp(ctx.Request.Context(), domain.User{ //ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})
	switch {
	case err == nil:
		ctx.String(http.StatusOK, "注册成功")
	case errors.Is(err, service.ErrDuplicateEmail):
		ctx.String(http.StatusOK, "邮箱冲突 请换一个")
	default:
		ctx.String(http.StatusOK, "系统错误")
	}
	//switch serviceErr {
	//case nil:
	//	ctx.String(http.StatusOK, "注册成功")
	//case service.ErrDuplicateEmail:
	//	ctx.String(http.StatusOK, "邮箱冲突 请换一个")
	//default:
	//	ctx.String(http.StatusOK, "系统错误")
	//}
}

func (h *UserHandler) Login(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	var req LoginReq
	//如果err不为空的话 Bind方法会返回错误
	if bindErr := ctx.Bind(&req); bindErr != nil {
		return
	}
	u, loginErr := h.svc.Login(ctx, req.Email, req.Password)
	switch {
	case loginErr == nil:
		sess := sessions.Default(ctx)
		sess.Set("userId", u.Id)
		sess.Options(sessions.Options{
			MaxAge: 900, //设置为15min 900秒
		})
		_ = sess.Save() //important
		ctx.String(http.StatusOK, "登陆成功")
	case errors.Is(loginErr, service.ErrInvalidUserOrPassword):
		ctx.String(http.StatusOK, "用户不存在或密码错误")
	default:
		ctx.String(http.StatusOK, "系统错误")
	}

	//switch loginErr {
	//case nil:
	//	ctx.String(http.StatusOK, "登陆成功")
	//case service.ErrInvalidUserOrPassword:
	//	ctx.String(http.StatusOK, "用户不存在或密码错误")
	//default:
	//	ctx.String(http.StatusOK, "系统错误")
	//}
}
func (h *UserHandler) LoginJWT(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	var req LoginReq
	//如果err不为空的话 Bind方法会返回错误
	if bindErr := ctx.Bind(&req); bindErr != nil {
		return
	}
	u, loginErr := h.svc.Login(ctx, req.Email, req.Password)
	switch {
	case loginErr == nil:
		err := h.SetLoginToken(ctx, u.Id)
		if err != nil {
			ctx.String(http.StatusOK, "系统错误")
			return
		}
		ctx.String(http.StatusOK, "登陆成功")
	case errors.Is(loginErr, service.ErrInvalidUserOrPassword):
		ctx.String(http.StatusOK, "用户不存在或密码错误")
	default:
		ctx.String(http.StatusOK, "系统错误")
	}
}

func (h *UserHandler) Profile(ctx *gin.Context) {
	//userId := sessions.Default(ctx).Get("userId")
	//userIdInt, ok := userId.(int64)
	//if !ok { //理论上不会出现 中间件已经校验过session了
	//	ctx.String(http.StatusOK, "登录失效 请重新登录")
	//	return
	//}
	uc, ok := ctx.Get("user")
	if !ok {
		ctx.String(http.StatusOK, "登录失效 请重新登录")
		return
	}
	uc1, ok1 := uc.(ijwt.UserClaims)
	if !ok1 {
		ctx.String(http.StatusOK, "登录失效 请重新登录")
		return
	}
	userIdInt := uc1.Uid

	u, findErr := h.svc.FindByUserId(ctx, userIdInt)
	if findErr != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.JSON(http.StatusOK, map[string]interface{}{
		"email":       u.Email,
		"userName":    u.UserName,
		"birthday":    u.Birthday.Format(time.DateOnly),
		"userProfile": u.UserProfile,
	})
}
func (h *UserHandler) Edit(ctx *gin.Context) {
	//解析请求
	type EditReq struct {
		UserName    string `json:"userName"`
		Birthday    string `json:"birthday"` // YYYY-MM-DD 格式
		UserProfile string `json:"userProfile"`
	}
	var req EditReq
	if bindErr := ctx.Bind(&req); bindErr != nil {
		return
	}
	//解析参数 请求service
	parseBirthday, parseErr := time.Parse(time.DateOnly, req.Birthday)
	if parseErr != nil {
		ctx.String(http.StatusOK, "生日格式不对")
		return
	}
	//userId := sessions.Default(ctx).Get("userId")
	//userIdInt, ok := userId.(int64)
	//if !ok { //理论上不会出现 中间件已经校验过session了
	//	ctx.String(http.StatusOK, "登录失效 请重新登录")
	//	return
	//}
	uc, ok := ctx.Get("user")
	if !ok {
		ctx.String(http.StatusOK, "登录失效 请重新登录")
		return
	}
	uc1, ok1 := uc.(ijwt.UserClaims)
	if !ok1 {
		ctx.String(http.StatusOK, "登录失效 请重新登录")
		return
	}
	userIdInt := uc1.Uid
	editErr := h.svc.Edit(ctx, domain.User{
		Id:          userIdInt,
		Birthday:    parseBirthday,
		UserName:    req.UserName,
		UserProfile: req.UserProfile,
	})
	switch {
	case editErr == nil:
		ctx.String(http.StatusOK, "修改成功")
	case errors.Is(editErr, service.ErrUserNameOrProfileTooLong):
		ctx.String(http.StatusOK, "用户名或个人简介超出字数限制")
	default:
		ctx.String(http.StatusOK, "系统错误")
	}
}
func (h *UserHandler) SendSMSLoginCode(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		return
	}
	//可以校验req
	if req.Phone == "" {
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "请输入手机号",
		})
		return
	}
	err := h.codeSvc.Send(ctx, bizLogin, req.Phone)
	switch {
	case err == nil:
		ctx.JSON(http.StatusOK, Result{
			Msg: "发送成功",
		})
	case errors.Is(err, service.ErrCodeSendTooMany):
		zap.L().Warn("频繁发送验证码")
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "短信发送频繁 请稍后再试",
		})
	default:
		log.Default().Printf("SendSMSLoginCode Send error %v", err)
		ctx.JSON(http.StatusOK, Result{
			Code: 5,
			Msg:  "系统错误",
		})
	}
}
func (h *UserHandler) LoginSMS(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
		Code  string `json:"code"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		return
	}
	ok, err := h.codeSvc.Verify(ctx, bizLogin, req.Phone, req.Code)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Code: 5,
			Msg:  "系统错误",
		})
		//在生产环境 手机号码是敏感信息 不能打印到日志里
		zap.L().Error("手机验证码验证失败", zap.Error(err))
		return
	}
	if !ok {
		ctx.JSON(http.StatusOK, Result{
			Code: 4,
			Msg:  "验证码不对 请重新输入",
		})
		return
	}
	//验证码是正确的 登录或注册用户 设置token
	u, err := h.svc.FindOrCreate(ctx, req.Phone)
	if err != nil {
		log.Default().Printf("LoginSMS FindOrCreate error %v", err)
		ctx.JSON(http.StatusOK, Result{
			Code: 5,
			Msg:  "系统错误",
		})
		return
	}
	err = h.SetLoginToken(ctx, u.Id)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "登陆成功",
	})
}

func (h *UserHandler) RefreshToken(ctx *gin.Context) {
	//约定前端在Authorization里放了长token
	tokenStr := h.ExtractToken(ctx)
	var rc ijwt.RefreshClaims
	token, err := jwt.ParseWithClaims(tokenStr, &rc, func(token *jwt.Token) (interface{}, error) {
		return ijwt.RefreshTokenKey, nil
	})
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	if token == nil || !token.Valid {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	//看ssid是否过期 过期表示退出登录的状态
	err = h.CheckSession(ctx, rc.Ssid)
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	err = h.SetJWTToken(ctx, rc.Uid, rc.Ssid)
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "OK",
	})
}

func (h *UserHandler) LogoutJWT(ctx *gin.Context) {
	//标记ssid不可用 加入redis
	//将长短token设置为非法值
	err := h.ClearToken(ctx)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Msg: "系统错误",
		})
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "OK",
	})
}
